﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GLWrapper;
using GameBaseCode;
//using SOIL;

namespace plane_game
{
    class Heightmap
    {

        Vector3[] vertexArray;
        Vector3[] colorArray;
        Vector3[] normalArray;
        ushort[] indexArray;
        float[,] heights;
        int terrainWidth, terrainLength;

        float scale = 5.0f;
        float yScale = 2.0f;

        public Heightmap(string file)
        {
            int width = 0;
            int height = 0;
            int channels = 0;
            
            unsafe
            {
                byte* imagePtr = (byte*)0;
                imagePtr = SOIL.SOIL_load_image(file, &width, &height, &channels, SOIL.SOIL_LOAD_RGBA);
                if (imagePtr == (byte*)0) throw new Exception("Heightmap. No img found");
                terrainWidth = width;
                terrainLength = height;
                int stride = height * width;

                heights = new float[width, height];
                for (int x = 0; x < width; x++)
                    for (int y = 0; y < height; y++)
                    {
                        heights[x, y] = imagePtr[ (x*height+y)*4+0 ] / 255.0f * 30.0f;
                    }

                SOIL.SOIL_free_image_data(imagePtr);
            }

            generateMesh();
            //calcNormals();
            normalizeMesh();
        }

        public void draw()
        {
            RenderVA();
        }

        private void RenderVA()
        {
            GL.glColor3f(0.8f, 0.8f, 0.8f);
            GL.glEnableClientState(GL.GL_NORMAL_ARRAY);
            GL.glEnableClientState(GL.GL_COLOR_ARRAY);
            GL.glEnableClientState(GL.GL_VERTEX_ARRAY);
            //GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);

            unsafe
            {
                fixed (float* ptr = &colorArray[0].x)
                    GL.glColorPointer(3, GL.GL_FLOAT, 0, ptr);

                fixed (float* ptr = &normalArray[0].x)
                    GL.glNormalPointer(GL.GL_FLOAT, 0, ptr);

                //fixed (float* ptr = &texCoordArray[0].x)
                //    GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, ptr);

                fixed (float* ptr = &vertexArray[0].x)
                    GL.glVertexPointer(3, GL.GL_FLOAT, 0, ptr);

                fixed (ushort* ptr = &indexArray[0])
                    GL.glDrawElements(GL.GL_TRIANGLES, indexArray.Length, GL.GL_UNSIGNED_SHORT, ptr);

            }
            //GL.glDrawArrays(GL.GL_TRIANGLES, 0, triangles.Length*3);

            GL.glDisableClientState(GL.GL_VERTEX_ARRAY);  // disable vertex arrays
            GL.glDisableClientState(GL.GL_COLOR_ARRAY);
            GL.glDisableClientState(GL.GL_NORMAL_ARRAY);
           // GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY);

        }

        // draw bit lower than quads to circumvent z-fighting
        public void drawLines()
        {
            GL.glDisable(GL.GL_LIGHTING);

            GL.glBegin(GL.GL_LINES);
            for (int i = 0; i < terrainLength - 2; i++)
            {
                for (int j = 0; j < terrainWidth - 2; j++)
                {
                    int k = i * (terrainWidth-1) + j;
                    int k2 = (i+1) * (terrainWidth - 1) + j;
                    GL.glVertex(vertexArray[k + 0]);
                    GL.glVertex(vertexArray[k + 1]);

                    GL.glVertex(vertexArray[k + 0]);
                    GL.glVertex(vertexArray[k2]);
                }
            }
            GL.glEnd();
        }


        private void generateMesh()
        {
            List<Vector3> verts = new List<Vector3>();
            List<Vector3> colors = new List<Vector3>();
            //List<ushort> idxList = new List<ushort>();
            indexArray = new ushort[(terrainLength - 1) * (terrainWidth - 1) * 6];

            // TODO. wtf
            float startW = 0;// -32 * scale;
            float startL = 0;//32 * scale;
            
            for (int i = 0; i < terrainLength - 1; i++) // y
            {
                for (int j = 0; j < terrainWidth-1; j++) // x
                {
                    verts.Add(new Vector3(scale * (startW + (j + 0)), getHeightInt(j + 0, i + 0) * yScale, scale * (startL - (i + 0))) );

                    colors.Add(new Vector3(0, getHeightInt(j + 0, i + 0) /20.0f , 0));
                }
            }

            int k=0;
            for (int i = 0; i < terrainLength - 2; i+=1) // y
            {
                for (int j = 0; j < terrainWidth - 2; j++) // x
                {
                    int idx = i * (terrainWidth - 1) + j;
                    int idxx = i * (terrainWidth - 1) + (j+1);
                    int nextRow = (i + 1) * (terrainWidth - 1) + j;
                    int nextRowP1 = (i + 1) * (terrainWidth - 1) + (j + 1);

                    indexArray[k++] = (ushort)(idx);
                    indexArray[k++] = (ushort)(nextRow);
                    indexArray[k++] = (ushort)(idxx);

                    indexArray[k++] = (ushort)(nextRow);
                    indexArray[k++] = (ushort)(nextRowP1);
                    indexArray[k++] = (ushort)(idxx);

                    //idxList.Add((ushort)idx);
                    //idxList.Add((ushort)nextRow);
                    //idxList.Add((ushort)idxx);

                    //idxList.Add((ushort)nextRow);
                    //idxList.Add((ushort)nextRowP1);
                    //idxList.Add((ushort)idxx);
                }
            }

            vertexArray = verts.ToArray();
            colorArray = colors.ToArray();
            //indexArray = idxList.ToArray();
            verts = null;
            colors = null;
            //idxList = null;

            

        }

        /* go thru all stored verts. For each quad there are 12 floats. xyz*4=12 */
        private void calcNormals()
        {
            normalArray = new Vector3[vertexArray.Length];
            int terrainSiz = (terrainWidth - 1) * (terrainLength - 1);
            int j = 0;
            for (int i = 0; i < terrainSiz * 4; i += 4)
            {
                // Calc vectors from vertexes (points)
                // v1 = t2-t1
                // v2 = t3-t1
                Vector3 v0 = vertexArray[j + 0];
                Vector3 v1 = vertexArray[j + 1];
                Vector3 v2 = vertexArray[j + 2];
                Vector3 v3 = vertexArray[j + 3];
                Vector3 v0v1 = v1 - v0;
                Vector3 v0v3 = v3 - v0;
                Vector3 v1v0 = v0 - v1;
                Vector3 v1v2 = v2 - v1;
                Vector3 v2v3 = v3 - v2;
                Vector3 v2v1 = v1 - v2;
                Vector3 v3v0 = v0 - v3;
                Vector3 v3v2 = v2 - v3;
                normalArray[j + 0] = Vector3.CrossProduct(v0v1, v0v3);
                normalArray[j + 1] = Vector3.CrossProduct(v1v2, v1v0);
                normalArray[j + 2] = Vector3.CrossProduct(v2v3, v2v1);
                normalArray[j + 3] = Vector3.CrossProduct(v3v0, v3v2);
                for (int k = 0; k < 4; k++)
                    normalArray[j + k].normalize();

                j += 4;
            }

        }

        public void normalizeMesh()
        {
            normalArray = new Vector3[vertexArray.Length];
            //Vert* vert = myself->vert;
            //Triangle* face = myself->face;

            for (int i = 0; i < vertexArray.Length; i++)
            {
                normalArray[i] = new Vector3(0.0f, 0.0f, 0.0f);
            }

            for (int i = 0; i < indexArray.Length; i+=3)
            {
                int ia = indexArray[i+0];
                int ib = indexArray[i+1];
                int ic = indexArray[i+2];

                Vector3 e1 = vertexArray[ia] - vertexArray[ib];
                Vector3 e2 = vertexArray[ic] - vertexArray[ib];
                Vector3 no = Vector3.CrossProduct(e1, e2);

                normalArray[ia] += no;
                normalArray[ib] += no;
                normalArray[ic] += no;
            }

            Vector3 flatVector = new Vector3(0,0,1);
            for (int i = 0; i < vertexArray.Length; i++)
            {
                if (normalArray[i].Magnitude > 0)
                    normalArray[i].normalize();

                float angleFactor = (float)Math.Acos(Vector3.Dot(normalArray[i], flatVector));
                colorArray[i].x = angleFactor * 0.125f + 0.125f;
                colorArray[i].y = angleFactor * 0.5f + 0.125f;
                colorArray[i].z = angleFactor * 0.125f + 0.125f;
            }
        }


        public float getHeightInt(int x, int z)
        {
           // if you get height outside of heightmap
           if(x < 0 || z < 0 || x > terrainWidth || z > terrainLength)
           {
               throw new Exception("out of bounds");
           }
           // using row-major, fastest in c and evaldraw
           // offset = row*NUMCOLS + column
           return heights[x,z];
        }

//        // src: http://www.gamedev.net/community/forums/topic.asp?topic_id=451312
//        /* bilinear interpolation of a quad height is also explained here
//        http://ilovevb.net/Web/blogs/vbxna/archive/2007/12/28/heightmap-collision-sample-posted.aspx
//        */
//      public static float getHeight(float x,float z)
//        {  
//           xa = floor(x); za = floor(z);
//           xb = floor(x+1); zb = floor(z+1);   

//           xd = x-floor(x);
//           if(xd < 0.0) xd *= -1;
//           zd = z-floor(z);
//           if(zd < 0.0) zd *= -1;
//           // Bilinear interpolation between x and y.
//           b = lerp( getHeightInt(xa,zb), getHeightInt(xb,zb), xd); 
//           a = lerp( getHeightInt(xa,za), getHeightInt(xb,za), xd); 
//           h = lerp(a,b,zd);
//           return h;
//        }
        // lerp 1 dimensional. use t-values from 0-1
        public static float lerp(float a, float b, float t)
        {
           return (b-a)*t+a;
        }




        //public bool intersectAtPoint(float x, float y, float z)
        //{
        //    float h = getHeightInt((int) (x / scale), (int) (z / scale) );
        //    return h > y / yScale;
        //}
    }
}
